using System;
using System.Collections.Generic;
using System.Text;
using System.Drawing;
using System.Runtime.Serialization;

namespace Shrapnel.Areas
{
	/// <summary>
	/// A definition of a certain region in our view, made up out of several AreaParts. 
	/// </summary>
	[DataContract()]	
	public class Area : ICollection<Part>
	{
		/// <summary>
		/// The parts this area consists of.
		/// </summary>
		[DataMember(Name="Parts")]
		private List<Part> parts = new List<Part>();

		/// <summary>
		/// The bounding box for this whole area, it's a cached version.
		/// </summary>
		private BoundingBox boundingBox;


		/// <summary>
		/// The top left position of this area in world space.
		/// </summary>
		public PointF TopLeftPosition 
		{
			get { return boundingBox.Location; }
			set { MoveToPosition(value); }
		}

		/// <summary>
		/// The bounding box of this area
		/// </summary>
		public BoundingBox AreaBoundingBox
		{
			get { return boundingBox; }
		}


		#region ICollection<Part> Members

		public void Add(Part item)
		{
			parts.Add(item);
			Update();
		}

		public void Clear()
		{
			parts.Clear();
			Update();
		}

		public bool Contains(Part item)
		{
			return parts.Contains(item);
		}

		public void CopyTo(Part[] array, int arrayIndex)
		{
			parts.CopyTo(array, arrayIndex);
		}

		public int Count
		{
			get { return parts.Count; }
		}

		public bool IsReadOnly
		{
			get { return false; }
		}

		public bool Remove(Part item)
		{
			bool result = parts.Remove(item);
			if (result)
			{
				Update();
			}
			return result;
		}

		#endregion

		#region IEnumerable<Part> Members

		public IEnumerator<Part> GetEnumerator()
		{
			return parts.GetEnumerator();
		}

		#endregion

		#region IEnumerable Members

		System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
		{
			return parts.GetEnumerator();
		}

		#endregion


		/// <summary>
		/// Update all cached properties that depend on the various parts we consist of. This need to be called
		/// when modifying the area occupied by a any of its parts.
		/// 
		/// Currently only our cached bbox has to be updated.
		/// </summary>
		public void Update()
		{
			boundingBox = RecalculateBoundingBox();
		}


		/// <summary>
		/// Move our area to the provided position, so that our area's topleftposition is equal to that position.
		/// </summary>
		/// <param name="position">Position to move to in world space</param>
		public void MoveToPosition(PointF position)
		{
			PointF delta = new PointF(
				position.X - boundingBox.Location.X,
				position.Y - boundingBox.Location.Y);

			foreach (Part part in parts)
			{
				part.Move(delta);
			}

			boundingBox.Location = position;
		}

		/// <summary>
		/// Calculate the bounding rectangle for this area
		/// </summary>
		/// <returns>Bounding rectangle in world coordinates</returns>
		private BoundingBox RecalculateBoundingBox()
		{
			if (parts.Count > 0)
			{
				BoundingBox bbox = parts[0].GetBoundingBox();

				for (int i=1; i<parts.Count; ++i)
				{
					bbox.Union(parts[i].GetBoundingBox());
				}

				return bbox;

			} else 
			{
				// return empty bbox ?!
				return new BoundingBox();
			}
		}


		/// <summary>
		/// See if we overlap with any of this area's parts
		/// </summary>
		/// <param name="rectangle"></param>
		/// <returns></returns>
		public bool OverlapsWith(BoundingBox bbox)
		{
			if (boundingBox.OverlapsWith(bbox))
			{
				foreach (Part part in parts)
				{
					if (part.OverlapsWith(bbox))
					{
						// we overlap! at least this part does, which is good enough!
						return true; 
					}
				}
			} 
			
			return false; // no overlap at all
		}
	}
}
